﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NetDisk.Dtos;
using NetDisk.Models;
using NetDisk.Repository;
using NetDisk.ViewModels;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NetDisk.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class FilesController : ControllerBase
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly IDirIndexRepository _dirIndexRepository;

        public FilesController(ApplicationDbContext dbContext, IDirIndexRepository dirIndexRepository)
        {
            _dbContext = dbContext;
            _dirIndexRepository = dirIndexRepository;
        }
        // GET api/values
        [HttpGet]
        public IEnumerable<FileViewModel> Get(
            [FromQuery(Name = "parent_id")]int? parentId,
            [FromQuery(Name = "search")]string Search)
        {
            var files = _dbContext.UploadFiles
                .GroupJoin(_dbContext.DirIndexes,
                    f => f.ParentId,
                    di => di.DirId,
                    (file, dirIndex) => new { file, dirIndex })
                    .SelectMany(f => f.dirIndex.DefaultIfEmpty(),
                        (file, dirIndex) => new { file, dirIndex });

            if (!string.IsNullOrEmpty(Search))
            {
                files = files.Where(f => f.file.file.RealName.ToUpper().Contains(Search.ToUpper()));
            }

            if (parentId != null)
            {
                if (parentId == 0)
                {
                    files = files.Where(f => f.file.file.ParentId == null);
                }
                else
                {
                    files = files.Where(f => f.file.file.ParentId == parentId);
                }
            }

            var result = files.OrderByDescending(f => f.file.file.IsDir)
                .Select(f => new FileViewModel
                {
                    Id = f.file.file.Id,
                    IsDir = f.file.file.IsDir,
                    FileName = f.file.file.RealName,
                    ParentIndex = string.IsNullOrEmpty(f.dirIndex.ParentIndex) ? null : JsonConvert.DeserializeObject<JArray>(f.dirIndex.ParentIndex),
                    CreatedAt = f.file.file.CreatedAt
                })
                .AsNoTracking()
                .ToList();

            return result;
        }

        [HttpGet("{id}")]
        public FileViewModel Get(int id)
        {
            if (id == 0)
            {
                return new FileViewModel
                {
                    Id = 0,
                    IsDir = true,
                    FileName = "/",
                    ParentIndex = null,
                    CreatedAt = DateTimeOffset.Now.ToUnixTimeMilliseconds()
                };
            }

            var result = _dbContext.UploadFiles
                .Join(_dbContext.DirIndexes,
                    f => f.Id,
                    idx => idx.DirId,
                    (file, dirIndex) => new { file, dirIndex })
                .Where(f => f.file.Id == id)
                .Select(f => new FileViewModel
                {
                    Id = f.file.Id,
                    IsDir = f.file.IsDir,
                    FileName = f.file.RealName,
                    ParentIndex = string.IsNullOrEmpty(f.dirIndex.ParentIndex) ? null : JsonConvert.DeserializeObject<JArray>(f.dirIndex.ParentIndex),
                    CreatedAt = f.file.CreatedAt
                })
                .AsNoTracking()
                .FirstOrDefault();

            return result;
        }

        [Authorize]
        [HttpPost]
        [RequestSizeLimit(512_000_000)]
        [RequestFormLimits(MultipartBodyLengthLimit = 512_000_000)]
        public int Create([FromForm]CreateFileDto createFileDto)
        {
            if (createFileDto.File.Length > 0)
            {
                var uploadDir = Startup.UploadPath;
                if (!Directory.Exists(uploadDir))
                    Directory.CreateDirectory(uploadDir);

                var parentId = createFileDto.ParentId == 0 ? null : createFileDto.ParentId as int?;
                var relativePath = createFileDto.RelativePath.TrimEnd((createFileDto.Filename).ToCharArray());
                if (!string.IsNullOrEmpty(relativePath))
                {
                    var parentIndex = _dbContext.DirIndexes.Where(di => di.DirId == parentId).FirstOrDefault();
                    var parentPath = parentIndex?.DirPath ?? "/";
                    var fullPath = $"{parentIndex?.DirPath ?? "/"}{relativePath}";
                    parentId = CreateFileDir(fullPath);
                }

                var uploadFileName = Guid.NewGuid().ToString();
                var uploadFilePath = Path.Combine(uploadDir, uploadFileName);
                using (var stream = new FileStream(uploadFilePath, FileMode.Create))
                {
                    createFileDto.File.CopyTo(stream);
                }

                var now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
                var uploadFile = new UploadFile
                {
                    FileName = uploadFileName,
                    RealName = createFileDto.File.FileName,
                    ParentId = parentId,
                    IsDir = false,
                    CreatedAt = now,
                    UpdatedAt = now
                };
                _dbContext.UploadFiles.Add(uploadFile);
                _dbContext.SaveChanges();
                return uploadFile.Id;
            }

            return 0;
        }

        private int CreateFileDir(string fullPath)
        {
            var dirPaths = fullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
            var parentId = 0;
            for (int i = 0; i < dirPaths.Count(); i++)
            {
                var path = string.Join('/', dirPaths.Take(i + 1).ToList());
                var parentIndex = _dbContext.DirIndexes
                    .Where(di => di.DirPath == $"/{path}/")
                    .FirstOrDefault();
                if (parentIndex != null)
                {
                    parentId = parentIndex.DirId;
                }
                else
                {
                    parentId = _dirIndexRepository.Create(parentId, dirPaths[i]);
                }
            }

            return parentId;
        }

        [Authorize]
        [HttpDelete("{id}")]
        public int Delete(int id)
        {
            var file = _dbContext.UploadFiles
                .Where(f => f.Id == id)
                .FirstOrDefault();

            if (file != null)
            {
                using var trx = _dbContext.Database.BeginTransaction();
                var files = new List<UploadFile>();
                var uploadDir = Startup.UploadPath;
                if (!file.IsDir)
                {
                    System.IO.File.Delete(Path.Combine(uploadDir, file.FileName));
                }
                else // 递归删除文件夹和文件
                {
                    GetSubFiles(file.Id, files);
                    var dirs = files.Where(f => f.IsDir == true).Select(f => f.Id).ToList();
                    var dirIndexs = _dbContext.DirIndexes.Where(di => dirs.Contains(di.DirId))
                        .ToList();
                    _dbContext.UploadFiles.RemoveRange(files);
                    _dbContext.DirIndexes.RemoveRange(dirIndexs);
                }

                _dbContext.UploadFiles.Remove(file);
                var dir = _dbContext.DirIndexes.Where(di => di.DirId == file.Id && file.IsDir).FirstOrDefault();
                if (dir != null)
                {
                    _dbContext.DirIndexes.Remove(dir);
                }

                var result = _dbContext.SaveChanges();
                trx.Commit();

                foreach (var subFile in files)
                {
                    if (!subFile.IsDir)
                    {
                        System.IO.File.Delete(Path.Combine(uploadDir, subFile.FileName));
                    }
                }

                return result;
            }

            return 0;
        }

        void GetSubFiles(int parentId, List<UploadFile> files)
        {
            var subFiles = _dbContext.UploadFiles
                .Where(f => f.ParentId == parentId)
                .ToList();

            foreach (var subFile in subFiles)
            {
                files.Add(subFile);
                if (subFile.IsDir)
                {
                    GetSubFiles(subFile.Id, files);
                }
            }
        }
    }
}
